package com.yjzx.bean;

import com.yjzx.interfaces.CommonClasses;
import com.yjzx.interfaces.ModifierEnum;
import com.yjzx.util.CollectionUtil;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;

import java.util.ArrayList;
import java.util.List;

/**
 * @author yjzx
 * @date 2023/10/20
 * @description
 */
public class UpdateMethod extends JavassistInfo {
    private String name;
    private List<UpdateFormalParam> params;
    private String body;
    private List<UpdateAnnotate> annotates;

    private List<String> modifiers;
    private String returnType;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<UpdateFormalParam> getParams() {
        return params;
    }

    public void setParams(List<UpdateFormalParam> params) {
        this.params = params;
    }


    public List<UpdateAnnotate> getAnnotates() {
        return annotates;
    }

    public void setAnnotates(List<UpdateAnnotate> annotates) {
        this.annotates = annotates;
    }

    public void addAnnotate(UpdateAnnotate annotate) {
        if (CollectionUtil.isEmpty(this.annotates) && annotate != null) {
            this.annotates = new ArrayList<>();
            annotates.add(annotate);
        }
    }

    public List<String> getModifiers() {
        return modifiers;
    }

    public void setModifiers(List<String> modifiers) {
        this.modifiers = modifiers;
    }

    public String getBody() {
        return body;
    }

    public String getReturnType() {
        return returnType;
    }

    public void setReturnType(String returnType) {
        this.returnType = returnType;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public UpdateMethod(String name, List<UpdateFormalParam> params, String body, String returnType) {
        this.name = name;
        this.params = params;
        this.body = body;
        this.returnType = returnType;
    }

    public UpdateMethod() {
    }

    @Override
    public String toString() {
        return "MethodUpdate{" +
                "name='" + name + '\'' +
                ", params=" + params +
                ", body='" + body + '\'' +
                ", returnType='" + returnType + '\'' +
                '}';
    }

    public ParameterAnnotationsAttribute toParameterAnnotationsAttribute(ClassPool pool, CtClass ctClass, List<Annotation[]> paramArrays) {
        ClassFile classFile = ctClass.getClassFile();
        ConstPool constPool = classFile.getConstPool();
        ParameterAnnotationsAttribute attribute = new ParameterAnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        int l = 0;
        for (Annotation[] paramArray : paramArrays) {
            l = Math.max(paramArray.length, l);
        }
        Annotation[][] annotations = new Annotation[params.size()][l];
        for (int i = 0; i < paramArrays.size(); i++) {
            annotations[i] = paramArrays.get(i);
        }
        attribute.setAnnotations(annotations);
        return attribute;
    }

    public CtMethod toMethod(ClassPool pool, CtClass ctClass) {
        this.check();
        CtMethod ctMethod;
        try {
            try {
                if (this.params == null || this.params.size() == 0) {
                    ctMethod = ctClass.getDeclaredMethod(this.name);
                } else {

                    CtClass[] classes = new CtClass[this.params.size()];
                    List<Annotation[]> paramArrays = new ArrayList<>();
                    for (int i = 0; i < this.params.size(); i++) {
                        UpdateFormalParam param = this.params.get(i);
                        param.check();
                        List<UpdateAnnotate> annotateList = param.getAnnotates();
                        if (CollectionUtil.isNotEmpty(annotateList)) {
                            int size = annotateList.size();
                            Annotation[] annotations = new Annotation[size];
                            for (int i1 = 0; i1 < annotateList.size(); i1++) {
                                annotations[i] = annotateList.get(i).toAnnotation(pool, ctClass);
                            }
                            paramArrays.add(annotations);
                        }

                        classes[i] = CommonClasses.getCtClass(pool, this.params.get(i).getClassName());
                    }
                    ctMethod = ctClass.getDeclaredMethod(this.name, classes);
                    if (CollectionUtil.isNotEmpty(paramArrays)) {
                        ctMethod.getMethodInfo().addAttribute(toParameterAnnotationsAttribute(pool, ctClass, paramArrays));
                    }
                }
            } catch (NotFoundException e) {
                //添加方法
                String returnType = this.returnType;
                CtClass[] classes = new CtClass[this.params == null ? 0 : this.params.size()];
                if (this.params != null && this.params.size() > 0) {
                    for (int i = 0; i < this.params.size(); i++) {
                        classes[i] = CommonClasses.getCtClass(pool, this.params.get(i).getClassName());
                    }
                }
                ctMethod = new CtMethod(returnType == null ? CtClass.voidType : pool.getCtClass(returnType), this.name, classes, ctClass);
            }
            if (CollectionUtil.isNotEmpty(this.annotates)) {
                for (UpdateAnnotate annotate : this.annotates) {
                    ctMethod.getMethodInfo().addAttribute(annotate.toAnnotationAttribute(pool, ctClass));
                }
            }

            ctMethod.setBody(this.body);
            ctMethod.setModifiers(ModifierEnum.modifierToInt(this.modifiers));
        } catch (NotFoundException | CannotCompileException e) {
            throw new RuntimeException(e);
        }
        return ctMethod;
    }

    @Override
    public void check() {
        this.checkParam(this.name);
    }
}
